package connections

import (
	"fmt"
	"pegasus/utils"
	"time"

	"github.com/gorilla/websocket"
)

//Hub WebSocket链接
type Hub struct {
	utils.ILogger
	id      int64           //链接标识用户自己保证唯一
	conn    *websocket.Conn //具体的链接
	manager *HubManager     //通知更上层 Hub已经失效需要关闭
	read    chan HubMessage //每次读取的内容发送到chan中,所有用户共用 方便服务端按次序处理
	write   chan []byte     //要发送的内容
}

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

//NewHub 返回1个链接
func newHub(il utils.ILogger,
	id int64, conn *websocket.Conn,
	readChan chan HubMessage, hm *HubManager, clientQueueCache int) *Hub {
	hub := &Hub{
		ILogger: il,
		id:      id,
		conn:    conn,
		read:    readChan,
		write:   make(chan []byte, clientQueueCache),
		manager: hm,
	}
	return hub
}

//Start 启动读写例程
func (hub *Hub) Start() {
	go hub.readLoop()
	go hub.writeLoop()
}

//Close 主动关闭连接
func (hub *Hub) Close() {
	hub.conn.Close()
}

func (hub *Hub) Write(content []byte) error {
	select {
	case hub.write <- content: //写入Hub
		return nil
	default: //写入丢弃考虑加大缓存
		err := fmt.Errorf("id:%d, discard write message:%v", hub.id, content)
		hub.Error(err)
		return err
	}
}

func (hub *Hub) readLoop() {
	defer func() {
		//unregister
		//hub.conn.Close()
		hub.manager.Kickout(hub.id)
	}()
	hub.conn.SetReadLimit(maxMessageSize)
	hub.conn.SetReadDeadline(time.Now().Add(pongWait))
	hub.conn.SetPongHandler(func(string) error { hub.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })

	for {
		messageType, message, err := hub.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				hub.Infof("id:%s, %v", hub.id, err)
			}
			break
		}
		if messageType == websocket.CloseMessage {
			break
		}
		if len(message) == 0 {
			hub.Warnf("id:%s, empty message:%d", hub.id, messageType)
			continue
		}
		hubMsg := HubMessage{ID: hub.id, Content: message}
		select {
		case hub.read <- hubMsg:
		default: //读取丢弃考虑加大hub.read 缓存
			hub.Errorf("id:%d, discard write message:%v", hub.id, message)
		}
	}
}

func (hub *Hub) writeLoop() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		//hub.conn.Close()
		hub.manager.Kickout(hub.id)
	}()
	for {
		select {
		case <-ticker.C:
			//web socket 需要定时发送ping pong消息
			hub.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := hub.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		case write, ok := <-hub.write:
			if !ok {
				return
			}
			hub.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := hub.conn.WriteMessage(websocket.BinaryMessage, write); err != nil {
				return
			}
		}
	}
}
